/**
 * Description:
 * History:  2017-10-23 Create
**/

package density.utils;


/**
 * @description 
 */
public class KPPosConvert {
    //public static IProjectedCoordinateSystem fromCS = (IProjectedCoordinateSystem) CoordinateSystemWktReader.Parse("PROJCS[\"hejl\",GEOGCS[\"GCS_Beijing_1954\",DATUM[\"D_Beijing_1954\", SPHEROID[\"Krasovsky_1940\", 6378245.0, 298.3]],PRIMEM[\"Greenwich\", 0.0],UNIT[\"Degree\", 0.017453292519943299]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"False_Easting\", 500000.0],PARAMETER[\"False_Northing\", 0.0],PARAMETER[\"Central_Meridian\", 108],PARAMETER[\"Scale_Factor\", 1],PARAMETER[\"Latitude_Of_Origin\", 0],UNIT[\"Meter\", 1]];");

    //public static IGeographicCoordinateSystem toCS = (IGeographicCoordinateSystem) fromCS.getGeographicCoordinateSystem();
    
    private double central_meridian;
    private double _semiMajor = 6378245.0;
    private double inverseFlattening = 298.3;

    private double _semiMinor = 0;
//    private long esp = 
//    private double e;
    private double e0;
    private double e1;
    private double e2;
    private double e3;
    private double es;
    private double esp;
    private double false_easting;
    private double false_northing;
    private double lat_origin;
    private double ml0;
    private double scale_factor = 1;
//    private double _es;
    private double _metersPerUnit = 1; 
    
    public KPPosConvert() {
        super();
//        this.scale_factor = parameter.Value;
        this.central_meridian = Degrees2Radians(108);
        this.lat_origin = Degrees2Radians(0);
        
        this.false_easting = 50000 * _metersPerUnit;
        this.false_northing = 0 * _metersPerUnit;
        
        this._semiMinor = (1.0 - (1.0 / this.inverseFlattening)) * _semiMajor;

        this.es = 1.0 - Math.pow(this._semiMinor / this._semiMajor, 2.0);
//        this.e = Math.sqrt(this.es);
        this.e0 = e0fn(this.es);
        this.e1 = e1fn(this.es);
        this.e2 = e2fn(this.es);
        this.e3 = e3fn(this.es);
        this.ml0 = this._semiMajor * mlfn(this.e0, this.e1, this.e2, this.e3, this.lat_origin);
        this.esp = this.es / (1.0 - this.es);
        
    }
    public double[] transform(double x, double y ) {
        double[] d = BJ54PrjToGeo(new double[]{x, y});
        d = GpsToGoogle(d[1], d[0]);
        d = GoogleToBaidu(d[1], d[0]);  
        return d;
    }
    /**
     * 北京54转经纬度
     * @param pts
     * @return
     */
    public double[] BJ54PrjToGeo(double[] pts) {
        return MetersToDegrees(pts);
    }
    protected static double e0fn(double x)
    {
        return (1.0 - ((0.25 * x) * (1.0 + ((x / 16.0) * (3.0 + (1.25 * x))))));
    }

    protected static double e1fn(double x)
    {
        return ((0.375 * x) * (1.0 + ((0.25 * x) * (1.0 + (0.46875 * x)))));
    }

    protected static double e2fn(double x)
    {
        return (((0.05859375 * x) * x) * (1.0 + (0.75 * x)));
    }

    protected static double e3fn(double x)
    {
        return (((x * x) * x) * 0.011393229166666666);
    }
    protected static double mlfn(double e0, double e1, double e2, double e3, double phi)
    {
        return ((((e0 * phi) - (e1 * Math.sin(2.0 * phi))) + (e2 * Math.sin(4.0 * phi))) - (e3 * Math.sin(6.0 * phi)));
    }

    protected static double e4fn(double x)
    {
        double num = 1.0 + x;
        double num2 = 1.0 - x;
        return Math.sqrt(Math.pow(num, num) * Math.pow(num2, num2));
    }

    public double[] MetersToDegrees(double[] p)
    {
        long num16 = 6;
        double num17 = (p[0] * this._metersPerUnit) - this.false_easting;
        double x = (p[1] * this._metersPerUnit) - this.false_northing;
        double d = (this.ml0 + (x / this.scale_factor)) / this._semiMajor;
        double num2 = d;
        long num4 = 0;
        while (true)
        {
            double num3 = ((((d + (this.e1 * Math.sin(2.0 * num2))) - (this.e2 * Math.sin(4.0 * num2))) + (this.e3 * Math.sin(6.0 * num2))) / this.e0) - num2;
            num2 += num3;
            if (Math.abs(num3) <= 1E-10)
            {
                break;
            }
            if (num4 >= num16)
            {
                throw new RuntimeException("Latitude failed to converge");
            }
            num4++;
        }
        if (Math.abs(num2) < 1.5707963267948966)
        {
            double num5 = Math.sin(num2);
            double num6 = Math.cos(num2);
//            MapProjection.sincos(num2, out num5, out num6);
            double num7 = Math.tan(num2);
            double num8 = this.esp * Math.pow(num6, 2.0);
            double num9 = Math.pow(num8, 2.0);
            double num10 = Math.pow(num7, 2.0);
            double num11 = Math.pow(num10, 2.0);
            d = 1.0 - (this.es * Math.pow(num5, 2.0));
            double num12 = this._semiMajor / Math.sqrt(d);
            double num13 = (num12 * (1.0 - this.es)) / d;
            double num14 = num17 / (num12 * this.scale_factor);
            double num15 = Math.pow(num14, 2.0);
            double rad = num2 - ((((num12 * num7) * num15) / num13) * (0.5 - ((num15 / 24.0) * (((((5.0 + (3.0 * num10)) + (10.0 * num8)) - (4.0 * num9)) - (9.0 * this.esp)) - ((num15 / 30.0) * (((((61.0 + (90.0 * num10)) + (298.0 * num8)) + (45.0 * num11)) - (252.0 * this.esp)) - (3.0 * num9)))))));
            double num20 = adjust_lon(this.central_meridian + ((num14 * (1.0 - ((num15 / 6.0) * (((1.0 + (2.0 * num10)) + num8) - ((num15 / 20.0) * (((((5.0 - (2.0 * num8)) + (28.0 * num10)) - (3.0 * num9)) + (8.0 * this.esp)) + (24.0 * num11))))))) / num6));
            if (p.length < 3)
            {
                return new double[] { Radians2Degrees(num20), Radians2Degrees(rad) };
            }
            return new double[] { Radians2Degrees(num20), Radians2Degrees(rad), p[2] };
        }
        if (p.length < 3)
        {
            return new double[] { Radians2Degrees(1.5707963267948966 * sign(x)), Radians2Degrees(this.central_meridian) };
        }
        return new double[] { Radians2Degrees(1.5707963267948966 * sign(x)), Radians2Degrees(this.central_meridian), p[2] };
    }
    protected static double Radians2Degrees(double rad)
    {
        return (57.295779513082323 * rad);
    }    
    public double[] DegreesToMeters(double[] lonlat)
    {
//        double num4;
//        double num5;
        double num = Degrees2Radians(lonlat[0]);
        double val = Degrees2Radians(lonlat[1]);
        double num3 = 0.0;
        num3 = adjust_lon(num - this.central_meridian);
//        MapProjection.sincos(val, out num4, out num5);
        double num4 = Math.sin(val);
        double num5 = Math.cos(val);
        double x = num5 * num3;
        double num7 = Math.pow(x, 2.0);
        double num8 = this.esp * Math.pow(num5, 2.0);
        double num10 = Math.tan(val);
        double num9 = Math.pow(num10, 2.0);
//        this._es = 1.0 - ((this._semiMinor * this._semiMinor) / (this._semiMajor * this._semiMajor));
        double d = 1.0 - (this.es * Math.pow(num4, 2.0));
        double num12 = this._semiMajor / Math.sqrt(d);
        double num13 = this._semiMajor * mlfn(this.e0, this.e1, this.e2, this.e3, val);
        double num14 = (((this.scale_factor * num12) * x) * (1.0 + ((num7 / 6.0) * (((1.0 - num9) + num8) + ((num7 / 20.0) * ((((5.0 - (18.0 * num9)) + Math.pow(num9, 2.0)) + (72.0 * num8)) - (58.0 * this.esp))))))) + this.false_easting;
        double num15 = (this.scale_factor * ((num13 - this.ml0) + ((num12 * num10) * (num7 * (0.5 + ((num7 / 24.0) * ((((5.0 - num9) + (9.0 * num8)) + (4.0 * Math.pow(num8, 2.0))) + ((num7 / 30.0) * ((((61.0 - (58.0 * num9)) + Math.pow(num9, 2.0)) + (600.0 * num8)) - (330.0 * this.esp)))))))))) + this.false_northing;
        if (lonlat.length < 3)
        {
            return new double[] { (num14 / this._metersPerUnit), (num15 / this._metersPerUnit) };
        }
        return new double[] { (num14 / this._metersPerUnit), (num15 / this._metersPerUnit), lonlat[2] };
    }
    protected static double Degrees2Radians(double deg) {
        return (0.017453292519943295 * deg);
    }    
    protected static double adjust_lon(double x)
    {
        long num = 0;
        do
        {
            if (Math.abs(x) <= 3.1415926535897931)
            {
                return x;
            }
            if (((long) Math.abs((double) (x / 3.1415926535897931))) < 2)
            {
                x -= sign(x) * 6.2831853071795862;
            }
            else if (((long) Math.abs((double) (x / 6.2831853071795862))) < 2147483647.0)
            {
                x -= ((long) (x / 6.2831853071795862)) * 6.2831853071795862;
            }
            else if (((long) Math.abs((double) (x / 13493037698.238832))) < 2147483647.0)
            {
                x -= ((long) (x / 13493037698.238832)) * 13493037698.238832;
            }
            else if (((long) Math.abs((double) (x / 2.8976077779357651E+19))) < 2147483647.0)
            {
                x -= ((long) (x / 2.8976077779357651E+19)) * 2.8976077779357651E+19;
            }
            else
            {
                x -= sign(x) * 6.2831853071795862;
            }
            num++;
        }
        while (num <= 4.0);
        return x;
    }    
    protected static double sign(double x)
    {
        if (x < 0.0)
        {
            return -1.0;
        }
        return 1.0;
    }

    /**
     * 经纬度转Google
     * @param gps_lat
     * @param gps_lon
     * @return
     */
    public static double[] GpsToGoogle(double gps_lat, double gps_lon) {
        double gg_lat;
        double gg_lon;
        if (outOfChina(gps_lat, gps_lon)) {
            gg_lat = gps_lat;
            gg_lon = gps_lon;
        } else {
            double dLat = transformLat(gps_lon - 105.0, gps_lat - 35.0);
            double dLon = transformLon(gps_lon - 105.0, gps_lat - 35.0);
            double radLat = (gps_lat / 180.0) * 3.1415926535897931;
            double magic = Math.sin(radLat);
            magic = 1.0 - ((0.0066934216229659433 * magic) * magic);
            double sqrtMagic = Math.sqrt(magic);
            dLat = (dLat * 180.0) / ((6335552.7170004258 / (magic * sqrtMagic)) * 3.1415926535897931);
            dLon = (dLon * 180.0) / (((6378245.0 / sqrtMagic) * Math.cos(radLat)) * 3.1415926535897931);
            gg_lat = gps_lat + dLat;
            gg_lon = gps_lon + dLon;
        }
        return new double[]{gg_lat, gg_lon};
    }

    /**
     * Google转百度坐标
     * @param gg_lat
     * @param gg_lon
     * @return
     */
     public static double[] GoogleToBaidu(double gg_lat, double gg_lon) {
         double  bd_lat, bd_lon;
        double x = gg_lon;
        double y = gg_lat;
        double z = Math.sqrt((x * x) + (y * y)) + (2E-05 * Math.sin(y * 3.1415926535897931));
        double theta = Math.atan2(y, x) + (3E-06 * Math.cos(x * 3.1415926535897931));
        bd_lon = (z * Math.cos(theta)) + 0.0065;
        bd_lat = (z * Math.sin(theta)) + 0.006;
        return new double[]{bd_lat, bd_lon};
    }
     private static boolean outOfChina(double lat, double lon) {
         if (((lon >= 72.004) && (lon <= 137.8347)) && ((lat >= 0.8293) && (lat <= 55.8271)))
         {
             return false;
         }
         return true;
     }
     private static double transformLat(double x, double y) {
         return ((((((((-100.0 + (2.0 * x)) + (3.0 * y)) + ((0.2 * y) * y)) + ((0.1 * x) * y)) + (0.2 * Math.sqrt(Math.abs(x)))) + ((((20.0 * Math.sin((6.0 * x) * 3.1415926535897931)) + (20.0 * Math.sin((2.0 * x) * 3.1415926535897931))) * 2.0) / 3.0)) + ((((20.0 * Math.sin(y * 3.1415926535897931)) + (40.0 * Math.sin((y / 3.0) * 3.1415926535897931))) * 2.0) / 3.0)) + ((((160.0 * Math.sin((y / 12.0) * 3.1415926535897931)) + (320.0 * Math.sin((y * 3.1415926535897931) / 30.0))) * 2.0) / 3.0));
     }

     private static double transformLon(double x, double y)
     {
         return ((((((((300.0 + x) + (2.0 * y)) + ((0.1 * x) * x)) + ((0.1 * x) * y)) + (0.1 * Math.sqrt(Math.abs(x)))) + ((((20.0 * Math.sin((6.0 * x) * 3.1415926535897931)) + (20.0 * Math.sin((2.0 * x) * 3.1415926535897931))) * 2.0) / 3.0)) + ((((20.0 * Math.sin(x * 3.1415926535897931)) + (40.0 * Math.sin((x / 3.0) * 3.1415926535897931))) * 2.0) / 3.0)) + ((((150.0 * Math.sin((x / 12.0) * 3.1415926535897931)) + (300.0 * Math.sin((x / 30.0) * 3.1415926535897931))) * 2.0) / 3.0));
     }    
//    
//    public static double[] MeterToDegree(IProjectedCoordinateSystem fromCS, IGeographicCoordinateSystem toCS, double[] pts) {
//        ICoordinateTransformation trans = Proj2Geog(fromCS, toCS);
//        return trans.getMathTransform().Transform(pts);
//    }    
//    private static ICoordinateTransformation Proj2Geog(IProjectedCoordinateSystem source, IGeographicCoordinateSystem target)
//    {
////        if (source.getGeographicCoordinateSystem().EqualParams(target))
////        {
//            return new CoordinateTransformation(source, target, TransformType.Transformation, 
//                    CreateCoordinateOperation(source.getProjection(), source.getGeographicCoordinateSystem().getHorizontalDatum().getEllipsoid(), source.getLinearUnit()).Inverse(), "", "", -1, "", "");
////        }
////        ConcatenatedTransform mathTransform = new ConcatenatedTransform();
////        CoordinateTransformationFactory factory = new CoordinateTransformationFactory();
////        mathTransform.CoordinateTransformationList.Add(factory.CreateFromCoordinateSystems(source, source.GeographicCoordinateSystem));
////        mathTransform.CoordinateTransformationList.Add(factory.CreateFromCoordinateSystems(source.GeographicCoordinateSystem, target));
////        return new CoordinateTransformation(source, target, TransformType.Transformation, mathTransform, string.Empty, string.Empty, -1, string.Empty, string.Empty);
//    }  
//    
//    private static IMathTransform CreateCoordinateOperation(IProjection projection, IEllipsoid ellipsoid, ILinearUnit unit)
//    {
//        List<ProjectionParameter> parameters = new ArrayList<ProjectionParameter>(projection.getNumParameters());
//        for (int i = 0; i < projection.getNumParameters(); i++)
//        {
//            parameters.add(projection.GetParameter(i));
//        }
//        parameters.add(new ProjectionParameter("semi_major", ellipsoid.getSemiMajorAxis()));
//        parameters.add(new ProjectionParameter("semi_minor", ellipsoid.getSemiMinorAxis()));
//        parameters.add(new ProjectionParameter("unit", unit.getMetersPerUnit()));
//        
//        return new TransverseMercator(parameters);
////        switch (projection.getClassName().ToLower(CultureInfo.InvariantCulture).Replace(' ', '_'))
////        {
////            case "mercator":
////            case "mercator_1sp":
////            case "mercator_2sp":
////                return new Mercator(parameters);
////
////            case "transverse_mercator":
////                return new TransverseMercator(parameters);
////
////            case "albers":
////            case "albers_conic_equal_area":
////                return new AlbersProjection(parameters);
////
////            case "krovak":
////                return new KrovakProjection(parameters);
////
////            case "lambert_conformal_conic":
////            case "lambert_conformal_conic_2sp":
////            case "lambert_conic_conformal_(2sp)":
////                return new LambertConformalConic2SP(parameters);
////        }
//        throw new NotSupportedException(String.format("Projection {0} is not supported.", projection.getClassName()));
//    }    
//    
//    public enum TransformType {
//        Other,
//        Conversion,
//        Transformation,
//        ConversionAndTransformation        
//    }
}
